package com.ruoyi.netty.protocol;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

/**
 * Modbus TCP协议解码器
 * 处理粘包和CRC校验
 */
@Slf4j
public class ModbusDecoder extends ByteToMessageDecoder {
    
    // Modbus TCP消息最小长度 (MBAP头部 + 功能码)
    private static final int MIN_LENGTH = 8;
    
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        // 检查可读长度是否足够最小消息长度
        if (in.readableBytes() < MIN_LENGTH) {
            return; // 等待更多数据
        }
        
        // 标记读索引，以便回退
        in.markReaderIndex();
        
        // 读取MBAP头 (Modbus Application Protocol header)
        short transactionId = in.readShort();
        short protocolId = in.readShort();
        short length = in.readShort();
        byte unitId = in.readByte();
        
        // 验证协议ID (Modbus = 0)
        if (protocolId != 0) {
            log.warn("Invalid Modbus protocol ID: {}", protocolId);
            in.resetReaderIndex();
            return;
        }
        
        // 检查长度
        int messageLength = length & 0xFFFF; // 无符号短整型
        if (in.readableBytes() < messageLength - 1) { // -1是因为已经读取了unitId
            // 数据不完整，重置读索引并等待更多数据
            in.resetReaderIndex();
            return;
        }
        
        // 读取功能码
        byte functionCode = in.readByte();
        
        // 读取剩余数据
        ByteBuf data = in.readBytes(messageLength - 2); // -2因为已经读取了unitId和functionCode
        
        // 创建ModbusMessage对象并添加到输出列表
        ModbusMessage message = new ModbusMessage();
        message.setTransactionId(transactionId);
        message.setProtocolId(protocolId);
        message.setLength(length);
        message.setUnitId(unitId);
        message.setFunctionCode(functionCode);
        message.setData(data);
        
        out.add(message);
        
        log.debug("Decoded Modbus message: transactionId={}, unitId={}, functionCode={}, dataLength={}",
                transactionId, unitId, functionCode, data.readableBytes());
    }
} 